{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 自动求导机制\n",
    "本说明将概述Autograd如何工作并记录操作。了解这些并不是绝对必要的，但我们建议您熟悉它，因为它将帮助您编写更高效，更简洁的程序，并可帮助您进行调试。 \n",
    "## 从后向中排除子图\n",
    "每个变量都有两个标志： **requires_grad 和 volatile** 。它们都允许从梯度计算中精细地排除子图，并可以提高效率。 \n",
    "\n",
    "### requires_grad\n",
    "如果有一个单一的输入操作需要梯度，它的输出也需要梯度。相反，只有所有输入都不需要梯度，输出才不需要。如果其中所有的变量都不需要梯度进行，后向计算不会在子图中执行。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "False"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import torch\n",
    "from torch import nn\n",
    "from torch.autograd import Variable\n",
    "\n",
    "x = Variable(torch.randn(5, 5))\n",
    "y = Variable(torch.randn(5, 5))\n",
    "z = Variable(torch.randn(5, 5), requires_grad=True) \n",
    "a = x + y \n",
    "a.requires_grad "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "b = a + z\n",
    "b.requires_grad"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这个标志特别有用，当您想要冻结部分模型时，或者您事先知道不会使用某些参数的梯度。 \n",
    "\n",
    "例如，如果要对预先训练的CNN进行优化，只要切换冻结模型中的 requires_grad 标志就足够了，直到计算到最后一层才会保存中间缓冲区，\n",
    "其中的仿射变换将使用需要梯度的权重并且网络的输出也将需要它们。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torchvision\n",
    "from torch import optim\n",
    "\n",
    "model = torchvision.models.resnet18(pretrained=True) \n",
    "for param in model.parameters(): \n",
    "    param.requires_grad = False\n",
    "# Replace the last fully-connected layer\n",
    "# Parameters of newly constructed modules have requires_grad=True by default \n",
    "model.fc = nn.Linear(512, 100)\n",
    "\n",
    "# Optimize only the classifier \n",
    "optimizer = optim.SGD(model.fc.parameters(), lr=1e-2, momentum=0.9)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "###  volatile（现在已经不用了，这个在0.41版中已经被标注为过期了，现在可以直接使用Tensor）"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**纯粹的inference模式下推荐使用 volatile**，当你确定你甚至不会调用 .backward() 时,它比任何其他自动求导的设置更有效\n",
    "\n",
    "它将使用绝对最小的内存来评估模型。 volatile 也决定了 require_grad is False 。\n",
    "\n",
    "**volatile 不同于 require_grad** 的传递。\n",
    "如果一个操作甚至只有有一个 volatile 的输入，它的输出也将是 volatile 。 \n",
    "\n",
    "Volatility 比“不需要梯度”更容易传递——只需要一个 volatile 的输入即可得到一个 volatile 的输出，相对的，需要所有的输入“不需要梯度”才能得到不需要梯度的输出。\n",
    "\n",
    "使用volatile标志，您不需要更改模型参数的任何设置来用于inference。创建一个 volatile 的输入就够了，这将保证不会保存中间状态。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 张量（Tensor）\n",
    "--------\n",
    "\n",
    "``torch.Tensor``是这个包的核心类。如果设置\n",
    "``.requires_grad`` 为 ``True``，那么将会追踪所有对于该张量的操作。 \n",
    "当完成计算后通过调用 ``.backward()``，自动计算所有的梯度，\n",
    "这个张量的所有梯度将会自动积累到 ``.grad`` 属性。\n",
    "\n",
    "要阻止张量跟踪历史记录，可以调用``.detach()``方法将其与计算历史记录分离，并禁止跟踪它将来的计算记录。\n",
    "\n",
    "为了防止跟踪历史记录（和使用内存），可以将代码块包装在``with torch.no_grad()：``中。\n",
    "在评估模型时特别有用，因为模型可能具有`requires_grad = True`的可训练参数，但是我们不需要梯度计算。\n",
    "\n",
    "在自动梯度计算中还有另外一个重要的类``Function``.\n",
    "\n",
    "``Tensor`` and ``Function`` are interconnected and build up an acyclic\n",
    "graph, that encodes a complete history of computation. Each tensor has\n",
    "a ``.grad_fn`` attribute that references a ``Function`` that has created\n",
    "the ``Tensor`` (except for Tensors created by the user - their\n",
    "``grad_fn is None``).\n",
    "\n",
    "``Tensor`` 和 ``Function``互相连接并生成一个非循环图，它表示和存储了完整的计算历史。\n",
    "每个张量都有一个``.grad_fn``属性，这个属性引用了一个创建了``Tensor``的``Function``（除非这个张量是用户手动创建的，即，这个张量的\n",
    "``grad_fn`` 是 ``None``）。\n",
    "\n",
    "如果需要计算导数，你可以在``Tensor``上调用``.backward()``。 \n",
    "如果``Tensor``是一个标量（即它包含一个元素数据）则不需要为``backward()``指定任何参数，\n",
    "但是如果它有更多的元素，你需要指定一个``gradient`` 参数来匹配张量的形状。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[1., 1.],\n",
      "        [1., 1.]], requires_grad=True)\n"
     ]
    }
   ],
   "source": [
    "x = torch.ones(2, 2, requires_grad=True)\n",
    "print(x)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[1., 1.],\n",
      "        [1., 1.]], requires_grad=True)\n"
     ]
    }
   ],
   "source": [
    "x = torch.ones(2, 2, requires_grad=True)\n",
    "print(x)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 自动求导如何编码历史信息\n",
    "每个变量都有一个 .creator 属性，它指向把它作为输出的函数。\n",
    "\n",
    "这是一个由 Function 对象作为节点组成的有向无环图（DAG）的入口点，它们之间的引用就是图的边。每次执行一个操作时，一个表示它的新 Function 就被实例化，它的 forward() 方法被调用，并且它输出的 Variable 的创建者被设置为这个 Function 。然后，通过跟踪从任何变量到叶节点的路径，可以重建创建数据的操作序列，并自动计算梯度。\n",
    "\n",
    "**需要注意的一点是**，整个图在每次迭代时都是从头开始重新创建的，这就允许使用任意的 Python控制流语句，这样可以在每次迭代时改变图的整体形状和大小。在启动训练之前不必对所有可能的路径进行编码—— what you run is what you differentiate.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Variable上的In-place操作\n",
    "在自动求导中支持in-place操作是一件很困难的事情，我们在大多数情况下都不鼓励使用它们。\n",
    "\n",
    "Autograd的缓冲区释放和重用非常高效，并且很少场合下in-place操作能实际上明显降低内存的使用量。除非您在内存压力很大的情况下，否则您可能永远不需要使用它们。\n",
    "\n",
    "限制in-place操作适用性主要有两个原因：\n",
    "１．覆盖梯度计算所需的值。这就是为什么变量不支持 log_ 。它的梯度公式需要原始输入， 而虽然通过计算反向操作可以重新创建它，但在数值上是不稳定的，并且需要额外的工作， 这往往会与使用这些功能的目的相悖。\n",
    "\n",
    "２．每个in-place操作实际上需要实现重写计算图。不合适的版本只需分配新对象并保留对旧 图的引用，而in-place操作则需要将所有输入的 creator 更改为表示此操作的 Function 。这就 比较棘手，特别是如果有许多变量引用相同的存储（例如通过索引或转置创建的），并且如 果被修改输入的存储被任何其他 Variable 引用，则in-place函数实际上会抛出错误"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## In-place正确性检查\n",
    "每个变量保留有version counter，它每次都会递增，当在任何操作中被使用时。当 Function 保存任何用于后向的tensor时，还会保存其包含变量的version counter。一旦访问 self.saved_tensors ，它将被检查，如果它大于保存的值，则会引起错误。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.2"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {
    "height": "calc(100% - 180px)",
    "left": "10px",
    "top": "150px",
    "width": "273.188px"
   },
   "toc_section_display": true,
   "toc_window_display": true
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
